﻿using ETW.EventListeners.ObjectValues;

namespace ETW.EventListeners.Listeners
{
    /// <summary>
    /// 运行时事件监听器
    /// System.Runtime
    /// <para>
    /// 该事件监听器中的计数器作为 .NET 运行时 (CoreCLR) 的一部分发布，并在 RuntimeEventSource.cs 中进行维护。
    /// </para>
    /// <para>
    /// <ref>https://docs.microsoft.com/zh-cn/dotnet/core/diagnostics/available-counters#systemnetnameresolution-counters</ref>
    /// </para>
    /// </summary>
    public class RuntimeEventListener : EventListener
    {
        public RuntimeEventListener()
        {
        }

        #region Event 事件

        /// <summary>
        /// 自上次 GC 以来 GC 的时间百分比
        /// <para>
        /// % Time in GC since last GC (time-in-gc)
        /// </para>
        /// </summary>
        public event Action<MeanCounterPayload, EventWrittenEventArgs> OnTimeInGcWritten;
        /// <summary>
        /// 每个更新间隔分配的字节数
        /// <para>
        /// Allocation Rate (alloc-rate)
        /// </para>
        /// </summary>
        public event Action<SumCounterPayload, EventWrittenEventArgs> OnAllocRateWritten;
        /// <summary>
        /// 相对于所有系统 CPU 资源进程的 CPU 使用率百分比
        /// <para>
        /// CPU Usage (cpu-usage)
        /// </para>
        /// </summary>
        public event Action<MeanCounterPayload, EventWrittenEventArgs> OnCpuUsageWritten;
        /// <summary>
        /// 已发生的异常数
        /// <para>
        /// Exception Count (exception-count)
        /// </para>
        /// </summary>
        public event Action<SumCounterPayload, EventWrittenEventArgs> OnExceptionCountWritten;
        /// <summary>
        /// 根据 GC.GetTotalMemory(Boolean) 认为要分配的字节数
        /// <para>
        /// GC Heap Size (gc-heap-size)
        /// </para>
        /// </summary>
        public event Action<MeanCounterPayload, EventWrittenEventArgs> OnGCHeapSizeWritten;
        /// <summary>
        /// 每个更新间隔发生的第 0 代 GC 次数
        /// <para>
        /// Gen 0 GC Count (gen-0-gc-count)
        /// </para>
        /// </summary>
        public event Action<SumCounterPayload, EventWrittenEventArgs> OnGen0GcCountWritten;
        /// <summary>
        /// 第 0 代 GC 的字节数
        /// <para>Gen 0 Size (gen-0-size)</para>
        /// </summary>
        public event Action<MeanCounterPayload,EventWrittenEventArgs> OnGen0GcSizeWritten;
        /// <summary>
        /// 每个更新间隔发生的第 1 代 GC 次数
        /// <para>
        /// Gen 1 GC Count (gen-1-gc-count)
        /// </para>
        /// </summary>
        public event Action<SumCounterPayload, EventWrittenEventArgs> OnGen1GcCountWritten;
        /// <summary>
        /// 第 1 代 GC 的字节数
        /// <para>Gen 1 Size (gen-1-size)</para>
        /// </summary>
        public event Action<MeanCounterPayload, EventWrittenEventArgs> OnGen1GcSizeWritten;
        /// <summary>
        /// 每个更新间隔发生的第 2 代 GC 次数
        /// <para>
        /// Gen 2 GC Count (gen-2-gc-count)
        /// </para>
        /// </summary>
        public event Action<SumCounterPayload, EventWrittenEventArgs> OnGen2GcCountWritten;
        /// <summary>
        /// 第 2 代 GC 的字节数
        /// <para>Gen 2 Size (gen-2-size)</para>
        /// </summary>
        public event Action<MeanCounterPayload, EventWrittenEventArgs> OnGen2GcSizeWritten;
        /// <summary>
        /// 大型对象堆的字节数
        /// <para>LOH Size (loh-size)</para>
        /// </summary>
        public event Action<MeanCounterPayload, EventWrittenEventArgs> OnLOHSizeWritten;
        /// <summary>
        /// 已固定对象堆的字节数
        /// <para>POH Size (poh-size)</para>
        /// </summary>
        public event Action<MeanCounterPayload, EventWrittenEventArgs> OnPOHSizeWritten;
        /// <summary>
        /// GC 堆碎片
        /// <para>GC Fragmentation (gc-fragmentation)</para>
        /// </summary>
        public event Action<MeanCounterPayload, EventWrittenEventArgs> OnGcFragmentationWritten;
        /// <summary>
        /// 尝试锁定监视器时出现争用的次数，基于 Monitor.LockContentionCount
        /// https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.monitor.lockcontentioncount#System_Threading_Monitor_LockContentionCount
        /// <para>Monitor Lock Contention Count (monitor-lock-contention-count)</para>
        /// </summary>
        public event Action<SumCounterPayload, EventWrittenEventArgs> OnMonitorLockContentionCountWritten;
        /// <summary>
        /// 当前活动的 Timer 实例的计数，基于 Timer.ActiveCount
        /// https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.timer.activecount#System_Threading_Timer_ActiveCount
        /// <para>Number of Active Timers (active-timer-count)</para>
        /// </summary>
        public event Action<MeanCounterPayload, EventWrittenEventArgs> OnActiveTimerCountWritten;
        /// <summary>
        /// 在某个时间点加载到进程中的 Assembly 实例的计数
        /// <para>Number of Assemblies Loaded (assembly-count)</para>
        /// </summary>
        public event Action<MeanCounterPayload, EventWrittenEventArgs> OnAssemblyCountWritten;
        /// <summary>
        ///  迄今为止 ThreadPool 中已处理的工作项数
        ///  <para>ThreadPool Completed Work Item Count (threadpool-completed-items-count)</para>
        /// </summary>
        public event Action<SumCounterPayload, EventWrittenEventArgs> OnThreadPoolCompletedTimesCountWritten;
        /// <summary>
        /// ThreadPool 中当前已加入处理队列的工作项数
        /// <para>ThreadPool Queue Length (threadpool-queue-length)</para>
        /// </summary>
        public event Action<MeanCounterPayload, EventWrittenEventArgs> OnThreadPoolQueueLengthWritten;
        /// <summary>
        /// ThreadPool 中当前存在的线程池线程数，基于 ThreadPool.ThreadCount
        /// <para>
        /// ThreadPool Thread Count (threadpool-thread-count)
        /// </para>
        /// </summary>
        public event Action<MeanCounterPayload, EventWrittenEventArgs> OnThreadPoolThreadCountWritten;
        /// <summary>
        /// 某个时间点映射到进程上下文的物理内存量，基于 Environment.WorkingSet
        /// <para>Working Set (working-set)</para>
        /// </summary>
        public event Action<MeanCounterPayload, EventWrittenEventArgs> OnWorkingSetWritten;
        /// <summary>
        /// JIT 编译的 IL 的总大小，以字节为单位
        /// <para>IL Bytes Jitted (il-bytes-jitted)</para>
        /// </summary>
        public event Action<MeanCounterPayload, EventWrittenEventArgs> OnILBytesJittedWritten;
        /// <summary>
        /// JIT 编译的方法数
        /// <para>Method Jitted Count (method-jitted-count)</para>
        /// </summary>
        public event Action<MeanCounterPayload, EventWrittenEventArgs> OnMethodJittedCountWritten;
        /// <summary>
        /// 由 GC 所提交的字节数
        /// <para>
        /// GC Committed Bytes (gc-committed-bytes)
        /// </para>
        /// </summary>
        public event Action<SumCounterPayload, EventWrittenEventArgs> OnGcCommittedBytesWritten;

        #endregion

        protected override void OnEventSourceCreated(EventSource eventSource)
        {
            if (!eventSource.Name.Equals("System.Runtime")) return;

            // 启用时间，并设置事件等级、事件关键字及事件计数器间隔时间(单位秒)
            EnableEvents(eventSource, DefaultConfiguration.RuntimeEventListenerOptions.EventLevel, DefaultConfiguration.RuntimeEventListenerOptions.EventKeywords, new Dictionary<string, string> { ["EventCounterIntervalSec"] = DefaultConfiguration.RuntimeEventListenerOptions.EventCounterIntervalSec.ToString() });
        }

        protected override void OnEventWritten(EventWrittenEventArgs eventData)
        {
            if (!"EventCounters".Equals(eventData.EventName)) return;
            if (!"System.Runtime".Equals(eventData.EventSource.Name)) return;

            for (var i = 0; i < eventData.Payload.Count; i++)
            {
                if (eventData.Payload[i] is IDictionary<string, object> eventPayload)
                {
                    var counterName = GetName(eventPayload);

                    MeanCounterPayload meanCounterPayload = null;
                    SumCounterPayload sumCounterPayload = null;
                    var CounterType = eventPayload["CounterType"].ToString();

                    if (CounterType.Equals("Mean"))
                        meanCounterPayload = MeanCounterPayload.ConvertFrom(eventPayload);
                    else if (CounterType.Equals("Sum"))
                        sumCounterPayload = SumCounterPayload.ConvertFrom(eventPayload);
                    else continue;

                    switch (counterName)
                    {
                        case "time-in-gc":
                            if (OnTimeInGcWritten != null)
                                OnTimeInGcWritten.Invoke(meanCounterPayload, eventData);
                                //Task.Factory.StartNew(() => OnTimeInGcWritten.Invoke(meanCounterPayload, eventData));
                            break;
                        case "cpu-usage":
                            if (OnCpuUsageWritten != null)
                                OnCpuUsageWritten.Invoke(meanCounterPayload, eventData);
                            //Task.Factory.StartNew(() => { OnCpuUsageWritten.Invoke(meanCounterPayload, eventData); });
                            break;
                        case "working-set":
                            if (OnWorkingSetWritten != null)
                                OnWorkingSetWritten.Invoke(meanCounterPayload, eventData);
                            //Task.Factory.StartNew(() => { OnWorkingSetWritten.Invoke(meanCounterPayload, eventData); });
                            break;
                        case "gc-heap-size":
                            if (OnGCHeapSizeWritten != null) 
                                OnGCHeapSizeWritten.Invoke(meanCounterPayload, eventData);
                            //Task.Factory.StartNew(() => { OnGCHeapSizeWritten.Invoke(meanCounterPayload, eventData); });
                            break;
                        case "gen-0-gc-count":
                            if (OnGen0GcCountWritten != null)
                                OnGen0GcCountWritten.Invoke(sumCounterPayload, eventData);
                            //Task.Factory.StartNew(() => { OnGen0GcCountWritten.Invoke(sumCounterPayload, eventData); });
                            break;
                        case "gen-1-gc-count":
                            if (OnGen1GcCountWritten != null)
                                OnGen1GcCountWritten.Invoke(sumCounterPayload, eventData);
                            //Task.Factory.StartNew(() => { OnGen1GcCountWritten.Invoke(sumCounterPayload, eventData); });
                            break;
                        case "gen-2-gc-count":
                            if (OnGen2GcCountWritten != null)
                                OnGen2GcCountWritten.Invoke(sumCounterPayload, eventData);
                            //Task.Factory.StartNew(() => { OnGen2GcCountWritten.Invoke(sumCounterPayload, eventData); });
                            break;
                        case "gen-0-size":
                            if (OnGen0GcSizeWritten != null)
                                OnGen0GcSizeWritten.Invoke(meanCounterPayload, eventData);
                            //Task.Factory.StartNew(() => { OnGen0GcSizeWritten.Invoke(meanCounterPayload, eventData); });
                            break;
                        case "gen-1-size":
                            if (OnGen1GcSizeWritten != null)
                                OnGen1GcSizeWritten.Invoke(meanCounterPayload, eventData);
                            //Task.Factory.StartNew(() => { OnGen1GcSizeWritten.Invoke(meanCounterPayload, eventData); });
                            break;
                        case "gen-2-size":
                            if (OnGen2GcSizeWritten != null)
                                OnGen2GcSizeWritten.Invoke(meanCounterPayload, eventData);
                            //Task.Factory.StartNew(() => { OnGen2GcSizeWritten.Invoke(meanCounterPayload, eventData); });
                            break;
                        case "threadpool-thread-count":
                            if (OnThreadPoolThreadCountWritten != null)
                                OnThreadPoolThreadCountWritten.Invoke(meanCounterPayload, eventData);
                            //Task.Factory.StartNew(() => { OnThreadPoolThreadCountWritten.Invoke(meanCounterPayload, eventData); });
                            break;
                        case "monitor-lock-contention-count":
                            if (OnMonitorLockContentionCountWritten != null)
                                OnMonitorLockContentionCountWritten.Invoke(sumCounterPayload, eventData);
                            //Task.Factory.StartNew(() => { OnMonitorLockContentionCountWritten.Invoke(sumCounterPayload, eventData); });
                            break;
                        case "threadpool-queue-length":
                            if (OnThreadPoolQueueLengthWritten != null)
                                OnThreadPoolQueueLengthWritten.Invoke(meanCounterPayload, eventData);
                            //Task.Factory.StartNew(() => { OnThreadPoolQueueLengthWritten.Invoke(meanCounterPayload, eventData); });
                            break;
                        case "threadpool-completed-items-count":
                            if (OnThreadPoolCompletedTimesCountWritten != null)
                                OnThreadPoolCompletedTimesCountWritten.Invoke(sumCounterPayload, eventData);
                            //Task.Factory.StartNew(() => { OnThreadPoolCompletedTimesCountWritten.Invoke(sumCounterPayload, eventData); });
                            break;
                        case "alloc-rate":
                            if (OnAllocRateWritten != null)
                                OnAllocRateWritten.Invoke(sumCounterPayload, eventData);
                            //Task.Factory.StartNew(() => { OnAllocRateWritten.Invoke(sumCounterPayload, eventData); });
                            break;
                        case "active-timer-count":
                            if (OnActiveTimerCountWritten != null)
                                OnActiveTimerCountWritten.Invoke(meanCounterPayload, eventData);
                            //Task.Factory.StartNew(() => { OnActiveTimerCountWritten.Invoke(meanCounterPayload, eventData); });
                            break;
                        case "gc-fragmentation":
                            if (OnGcFragmentationWritten != null)
                                OnGcFragmentationWritten.Invoke(meanCounterPayload, eventData);
                            //Task.Factory.StartNew(() => { OnGcFragmentationWritten.Invoke(meanCounterPayload, eventData); });
                            break;
                        case "exception-count":
                            if (OnExceptionCountWritten != null)
                                OnExceptionCountWritten.Invoke(sumCounterPayload, eventData);
                            //Task.Factory.StartNew(() => { OnExceptionCountWritten.Invoke(sumCounterPayload, eventData); });
                            break;
                        case "loh-size":
                            if (OnLOHSizeWritten != null)
                                OnLOHSizeWritten.Invoke(meanCounterPayload, eventData);
                            //Task.Factory.StartNew(() => { OnLOHSizeWritten.Invoke(meanCounterPayload, eventData); });
                            break;
                        case "poh-size":
                            if (OnPOHSizeWritten != null)
                                OnPOHSizeWritten.Invoke(meanCounterPayload, eventData);
                            //Task.Factory.StartNew(() => { OnPOHSizeWritten.Invoke(meanCounterPayload, eventData); });
                            break;
                        case "assembly-count":
                            if (OnAssemblyCountWritten != null)
                                OnAssemblyCountWritten.Invoke(meanCounterPayload, eventData);
                            //Task.Factory.StartNew(() => { OnAssemblyCountWritten.Invoke(meanCounterPayload, eventData); });
                            break;
                        case "il-bytes-jitted":
                            if (OnILBytesJittedWritten != null)
                                OnILBytesJittedWritten.Invoke(meanCounterPayload, eventData);
                            //Task.Factory.StartNew(() => { OnILBytesJittedWritten.Invoke(meanCounterPayload, eventData); });
                            break;
                        case "method-jitted-count":
                            if (OnMethodJittedCountWritten != null)
                                OnMethodJittedCountWritten.Invoke(meanCounterPayload, eventData);
                            //Task.Factory.StartNew(() => { OnMethodJittedCountWritten.Invoke(meanCounterPayload, eventData); });
                            break;
                        case "gc-committed-bytes":
                            if (OnGcCommittedBytesWritten != null)
                                OnGcCommittedBytesWritten.Invoke(sumCounterPayload, eventData);
                            //Task.Factory.StartNew(() => { OnGcCommittedBytesWritten.Invoke(sumCounterPayload, eventData); });
                            break;
                    }
                }
            }
        }

        private static string GetName(IDictionary<string, object> eventPayload)
        {
            var counterName = "";
            if (eventPayload.TryGetValue("Name", out object displayValue))
            {
                counterName = displayValue.ToString();
            }

            return counterName;
        }

        private static string GetDisplayName(IDictionary<string, object> eventPayload)
        {
            var counterName = "";
            if (eventPayload.TryGetValue("DisplayName", out object displayValue))
            {
                counterName = displayValue.ToString();
            }

            return counterName;
        }

        private static (string counterName, string counterValue) GetRelevantMetric(IDictionary<string, object> eventPayload)
        {
            var counterName = "";
            var counterValue = "";

            if (eventPayload.TryGetValue("DisplayName", out object displayValue))
            {
                counterName = displayValue.ToString();
            }

            if (eventPayload.TryGetValue("Mean", out object value) || eventPayload.TryGetValue("Increment", out value))
            {
                counterValue = value.ToString();
            }

            return (counterName, counterValue);
        }
    }
}
